/*
 *访问方式： ftp://esp32:esp32@192.168.0.110
 * https://hub.fastgit.org/robo8080/ESP32_FTPServer_SD
 * 
 * https://github.com/MollySophia/ESP32_FTPServer_SD
 *
 * To upload through terminal you can use: curl -F "image=@firmware.bin" esp8266-webupdate.local/update
 */

#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <Update.h>
//#include "ESP32FtpServer.h"
#include "SD.h"
#include <FS.h>
#include <SimpleFTPServer.h>
#include "nmea/nmea.h"

#define WIFI_MODE_AP

const char* host = "esp32-lawson";

#ifdef WIFI_MODE_AP
const char *ssid = "LOC_BANK";
const char *password = "loc_bank";
#else
//填写wifi信息
const char *ssid = "CNOSTAR";
const char *password = "zhbd1803";
#endif


FtpServer ftpSrv;   //set #define FTP_DEBUG in ESP32FtpServer.h to see ftp verbose on serial
WiFiServer ser2net_server(8080);
WiFiClient client;

WebServer update_server(80);
const char* serverIndex = "<form method='POST' action='/update' enctype='multipart/form-data'><input type='file' name='update'><input type='submit' value='Update'></form>";


int poweron = true;
File file;
char file_path[64] = "/tmp.nmea";
char new_path[64] = "";
char file_dir[64] = "";

void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
  Serial.printf("Listing directory: %s\n", dirname);

  File root = fs.open(dirname);
  if(!root){
      Serial.println("Failed to open directory");
      return;
  }
  if(!root.isDirectory()){
      Serial.println("Not a directory");
      return;
  }

  File file = root.openNextFile();
  while(file){
      if(file.isDirectory()){
          Serial.print("  DIR : ");
          Serial.println(file.name());
          if(levels){
              listDir(fs, file.path(), levels -1);
          }
      } else {
          Serial.print("  FILE: ");
          Serial.print(file.name());
          Serial.print("  SIZE: ");
          Serial.println(file.size());
      }
      file = root.openNextFile();
  }

  root.close();
}
/*
void Serial_callback() {
    while (Serial1.available())
    {
        size_t n = Serial1.readBytes(data, sizeof(data));
        Serial.write(data,n);
    }
}
*/

extern "C" {
void nerror(char *buf, int n)
{
  Serial.println("");
  Serial.write(buf, n);
}
}


void _callback(FtpOperation ftpOperation, unsigned int freeSpace, unsigned int totalSpace){
	Serial.print(">>>>>>>>>>>>>>> _callback " );
	Serial.print(ftpOperation);
	/* FTP_CONNECT,
	 * FTP_DISCONNECT,
	 * FTP_FREE_SPACE_CHANGE
	 */
	Serial.print(" ");
	Serial.print(freeSpace);
	Serial.print(" ");
	Serial.println(totalSpace);

	// freeSpace : totalSpace = x : 360

	if (ftpOperation == FTP_CONNECT) Serial.println(F("CONNECTED"));
	if (ftpOperation == FTP_DISCONNECT) Serial.println(F("DISCONNECTED"));
};
void _transferCallback(FtpTransferOperation ftpOperation, const char* name, unsigned int transferredSize){
	Serial.print(">>>>>>>>>>>>>>> _transferCallback " );
	Serial.print(ftpOperation);
	/* FTP_UPLOAD_START = 0,
	 * FTP_UPLOAD = 1,
	 *
	 * FTP_DOWNLOAD_START = 2,
	 * FTP_DOWNLOAD = 3,
	 *
	 * FTP_TRANSFER_STOP = 4,
	 * FTP_DOWNLOAD_STOP = 4,
	 * FTP_UPLOAD_STOP = 4,
	 *
	 * FTP_TRANSFER_ERROR = 5,
	 * FTP_DOWNLOAD_ERROR = 5,
	 * FTP_UPLOAD_ERROR = 5
	 */
	Serial.print(" ");
	Serial.print(name);
	Serial.print(" ");
	Serial.println(transferredSize);
};

void setup(void){
  Serial.begin(115200);
  Serial.println("");
  Serial1.begin(115200, SERIAL_8N1, 1, 2);
//  Serial1.onReceive(Serial_callback);
  Serial1.setTimeout(20);

  pinMode(0, OUTPUT);
  digitalWrite(0, LOW); // sets the digital pin 13 on

#ifdef WIFI_MODE_AP
  // You can remove the password parameter if you want the AP to be open.
  // a valid password must have more than 7 characters
  WiFi.mode(WIFI_AP_STA);
  if (!WiFi.softAP(ssid, password)) {
    log_e("Soft AP creation failed.");
    while(1);
  }
  IPAddress myIP = WiFi.softAPIP();
  Serial.print("AP IP address: ");
  Serial.println(myIP);
#else
  WiFi.begin(ssid, password);
  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
#endif

  ser2net_server.begin();
  if (true) {
    MDNS.begin(host);
    update_server.on("/", HTTP_GET, []() {
      update_server.sendHeader("Connection", "close");
      update_server.send(200, "text/html", serverIndex);
    });
    update_server.on("/update", HTTP_POST, []() {
      update_server.sendHeader("Connection", "close");
      update_server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
      ESP.restart();
    }, []() {
      HTTPUpload& upload = update_server.upload();
      if (upload.status == UPLOAD_FILE_START) {
        Serial.setDebugOutput(true);
        Serial.printf("Update: %s\n", upload.filename.c_str());
        if (!Update.begin()) { //start with max available size
          Update.printError(Serial);
        }
      } else if (upload.status == UPLOAD_FILE_WRITE) {
        if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
          Update.printError(Serial);
        }
      } else if (upload.status == UPLOAD_FILE_END) {
        if (Update.end(true)) { //true to set the size to the current progress
          Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
        } else {
          Update.printError(Serial);
        }
        Serial.setDebugOutput(false);
      } else {
        Serial.printf("Update Failed Unexpectedly (likely broken connection): status=%d\n", upload.status);
      }
    });
    update_server.begin();
    MDNS.addService("http", "tcp", 80);

    Serial.printf("Ready! Open http://%s.local in your browser\n", host);
  } else {
    Serial.println("WiFi Failed");
  }

  int sd_ok = 0;
  while(sd_ok == 0)
  {
    if (SD.begin(SS))
    {
      sd_ok = 1;
      uint64_t cardSize = SD.cardSize() / (1024 * 1024);
      Serial.printf("SD Card Size: %lluMB\n", cardSize);
      Serial.printf("Total space: %lluMB\n", SD.totalBytes() / (1024 * 1024));
      Serial.printf("Used space: %lluMB\n", SD.usedBytes() / (1024 * 1024));
    
      listDir(SD, "/", 0);
      Serial.println("SD opened!");
      char file_mode[4] = FILE_WRITE;
      if(1 == SD.exists(file_path))
      {
        sprintf(file_mode, "%s", FILE_APPEND);
      }
      file = SD.open(file_path, file_mode);
      if(!file){
          Serial.println("Failed to open file for writing");
          return;
      }
    }
    else
    {
      Serial.println("SD card 初始化失败");
      //ToDo:挂载失败后重新给SD卡上电重新尝试挂载,也可以通sdfat库进行格式化，或者添加控制命令，用手机操作格式化。
      delay(2000);
    }
  }

  /////FTP Setup, ensure SD is started before ftp;  /////////
  ftpSrv.setCallback(_callback);
  ftpSrv.setTransferCallback(_transferCallback);
  ftpSrv.begin("esp32","esp32");    //username, password for ftp.  set ports in ESP32FtpServer.h  (default 21, 50009 for PASV)
/*
$PAIR062,0,1*3F
$PAIR062,1,0*3F
$PAIR062,2,0*3C
$PAIR062,3,0*3D
$PAIR062,4,1*3B
$PAIR062,5,1*3A
$PAIR062,6,1*39
$PAIR062,7,0*39
$PAIR062,8,0*36
$PAIR062,9,0*37
PAIR062,10,0*0F
$PAIR510,1*23
$PAIR513*3D
*/
  //初始化定位模组输出
  Serial1.print("$PAIR062,0,1*3F\r\n");
  Serial1.print("$PAIR062,1,0*3F\r\n");
  Serial1.print("$PAIR062,2,0*3C\r\n");
  Serial1.print("$PAIR062,3,0*3D\r\n");
  Serial1.print("$PAIR062,4,1*3B\r\n");
  Serial1.print("$PAIR062,5,1*3A\r\n");
  Serial1.print("$PAIR062,6,1*39\r\n");
  Serial1.print("$PAIR062,7,0*39\r\n");
  Serial1.print("$PAIR062,8,0*36\r\n");
  Serial1.print("$PAIR062,9,0*37\r\n");
  Serial1.print("PAIR062,10,0*0F\r\n");
  Serial1.print("$PAIR510,1*23\r\n");
  Serial1.print("$PAIR513*3D\r\n");
}

void del_dir()
{
  int min_dir = 0;
  File root = SD.open("/");
  if(!root){
      Serial.println("Failed to open directory");
      return;
  }
  if(!root.isDirectory()){
      Serial.println("Not a directory");
      return;
  }

  File file = root.openNextFile();
  while(file)
  {
    if(file.isDirectory())
    {
      int dir = atoi(file.name());
      if(dir < min_dir)
      {
        min_dir = dir;
      }
    }
    file = root.openNextFile();
  }
  root.close();

  if(min_dir != 0)
  {
    sprintf(file_dir, "/%d", min_dir);
    if(SD.rmdir(file_dir)){
        Serial.println("Dir removed");
    } else {
        Serial.println("rmdir failed");
    }
  }
}

int do_file(String &str)
{
  if(file.print(str)){
      //Serial.println("Message appended");
  } else {
      Serial.println("Append failed");
  }

  nmeaGPRMC rmc;
  memset(&rmc, 0, sizeof(nmeaGPRMC));
  int type = nmea_pack_type(str.c_str() + 1, str.length());
  if(type == GPRMC)
  {
    if(1 == nmea_parse_GPRMC(str.c_str(), str.length(), &rmc))
    {
      Serial.printf("rmc:%d %d %d %d %d %d\n", 1900 + rmc.utc.year, rmc.utc.mon + 1, rmc.utc.day, rmc.utc.hour, rmc.utc.min, rmc.utc.sec);
      if(poweron == true && rmc.utc.year != 0  && rmc.utc.mon != 0  && rmc.utc.day != 0  && rmc.utc.hour != 0  && rmc.utc.min != 0  && rmc.utc.sec)
      {
        poweron = false;
        file.close();
        Serial.printf("close file: %s\n", file_path);

        sprintf(file_dir, "/%d%02d%02d", 1900 + rmc.utc.year, rmc.utc.mon + 1, rmc.utc.day);
        if(SD.exists(file_dir) == 0)
        {
          if(SD.mkdir(file_dir)){
              Serial.println("Dir created");
          } else {
              Serial.println("mkdir failed");
          }
        }

        sprintf(new_path, "/%d%02d%02d/%d%02d%02d%02d%02d%02d.nmea", 1900 + rmc.utc.year, rmc.utc.mon + 1, rmc.utc.day, 1900 + rmc.utc.year, rmc.utc.mon + 1, rmc.utc.day, rmc.utc.hour, rmc.utc.min, rmc.utc.sec);
        SD.rename(file_path, new_path);
        file = SD.open(new_path, FILE_APPEND);
        Serial.printf("open file: %s\n", new_path);
        strcpy(file_path, new_path);
      }

      if(rmc.utc.hour == 0 && rmc.utc.min == 0 && rmc.utc.sec == 0)
      {
          file.close();
          Serial.printf("close file: %s\n", file_path);

          sprintf(file_dir, "/%d%02d%02d", 1900 + rmc.utc.year, rmc.utc.mon + 1, rmc.utc.day);
          if(SD.exists(file_dir) == 0)
          {
            if(SD.mkdir(file_dir)){
                Serial.println("Dir created");
            } else {
                Serial.println("mkdir failed");
            }
          }

          sprintf(new_path, "/%d%02d%02d/%d%02d%02d%02d%02d%02d.nmea", 1900 + rmc.utc.year, rmc.utc.mon + 1, rmc.utc.day, 1900 + rmc.utc.year, rmc.utc.mon + 1, rmc.utc.day, rmc.utc.hour, rmc.utc.min, rmc.utc.sec);
          file = SD.open(new_path, FILE_WRITE);
          Serial.printf("open file: %s\n", new_path);
          strcpy(file_path, new_path);
      }
    }
    file.flush();
  }

  //ToDo:循环录制
  int total = SD.totalBytes() / (1024 * 1024);
  int used = SD.usedBytes() / (1024 * 1024);
  int empty = total - used;

  if(used < 100)
  {
    del_dir();
  }
  return 0;
}

int do_trans()
{
  String str;
  ftpSrv.handleFTP();
  update_server.handleClient();
  if(Serial1.available())
  {
    str = Serial1.readStringUntil('\n');
    if(str.length() > 10)
    {
      /*Serial1.read();*/
      if(client)
      {
        client.print(str);
      }
      
      do_file(str);
    }
  }
  return 0;
}

void loop(void){
  client = ser2net_server.available();
  if (client) {                             // if you get a client,
      Serial.println("New Client.");           // print a message out the serial port
      String currentLine = "";                // make a String to hold incoming data from the client
      while (client.connected())
      {
        do_trans();
      }
  }
  else
  {
    do_trans();
  }
}
